package com.mzlalal.repeat.submit.aop;

import cn.hutool.core.text.StrBuilder;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.Method;
import com.alibaba.fastjson.JSON;
import com.mzlalal.repeat.submit.prop.RepeatSubmitProperties;
import com.mzlalal.repeat.submit.service.RepeatSubmitPatternService;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;

/**
 * 重复提交拦截切面
 *
 * @author Mzlalal
 * @date 2022/07/17 10:04
 */
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
@AllArgsConstructor
public class RepeatSubmitAspect implements MethodInterceptor {
    /**
     * redisson数据源
     */
    private final RedissonClient redissonClient;
    /**
     * 重复提交配置
     */
    private final RepeatSubmitProperties properties;
    /**
     * 重复提交路径匹配服务
     */
    private final RepeatSubmitPatternService patternService;

    /**
     * 是否允许GET请求
     *
     * @param request 请求
     * @return true 放行, false 拦截
     */
    private boolean allowGetMethod(HttpServletRequest request) {
        // 配置允许放行 && GET请求
        return properties.isSkipMethodGet() && Method.GET.name().equalsIgnoreCase(request.getMethod());
    }

    /**
     * 根据方法接收的参数、请求TOKEN来计算成为redis的key
     *
     * @param invocation 调用链
     * @param header     请求头
     * @return string 计算MD5字符串作为redis的key
     */
    private String calculateRedisKey(MethodInvocation invocation, String header) {
        // 拼接字符串
        StrBuilder strBuilder = StrBuilder.create()
                .append(JSON.toJSONString(invocation.getArguments()))
                .append(header);
        // 计算MD5
        return SecureUtil.md5(strBuilder.toString());
    }

    /**
     * 环绕通知
     *
     * @param invocation 调用链
     * @return Object 返回结果
     */
    @Override
    @SneakyThrows
    @SuppressWarnings("all")
    public Object invoke(MethodInvocation invocation) {
        // 获取spring保存在ThreadLocal的request对象
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 非request请求跳过
        if (requestAttributes == null) {
            return invocation.proceed();
        }
        // 获取request
        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        // 是否允许GET请求
        if (this.allowGetMethod(request)) {
            return invocation.proceed();
        }
        // 检查请求地址是否在拦截范围之内
        if (patternService.checkRequestUriWithPattern(request.getRequestURI())) {
            return invocation.proceed();
        }
        // TOKEN不存在则放行
        String header = request.getHeader(properties.getHeaderTokenName());
        if (StrUtil.isBlank(header)) {
            return invocation.proceed();
        }
        // 计算redisKey
        String redisKey = this.calculateRedisKey(invocation, header);
        // 获取锁
        RLock lock = redissonClient.getLock(redisKey);
        try {
            // 拿到锁后最小保持锁的时间
            boolean locked = lock.tryLock(0L, properties.getLeaseLockTime(), TimeUnit.SECONDS);
            // 获取锁失败,上次请求未处理完成或者没超过最小保持锁时间
            if (!locked) {
                // 获取response
                HttpServletResponse response = ((ServletRequestAttributes) requestAttributes).getResponse();
                if (response == null) {
                    return null;
                }
                // 输出JSON
                ServletUtil.write(response, properties.getTipJsonText(), "text/json; charset=UTF-8");
                // 执行
                return null;
            }
        } finally {
            // 解锁
            if (properties.isUnlockWhenSuccess() && lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        // 返回结果
        return invocation.proceed();
    }
}